/*
 * Copyright 2011 jmarsden.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cc.plural.jsonij.marshal;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import cc.plural.jsonij.JSON;
import cc.plural.jsonij.Value;

/**
 *
 * @author jmarsden
 */
public class JSONDocumentMarshaler {

    public static String innerArrayAttribute;
    public static String innerObjectAttribute;

    static {
        innerArrayAttribute = "_innerArray";
        innerObjectAttribute = "_innerObject";
    }

    public Object marshalJSONDocument(JSON json, Class<?> objectClass) throws JSONMarshalerException {
        Object resultObject = null;
        Value jsonRoot = json.getRoot();
        if (jsonRoot.type() == Value.TYPE.OBJECT) {
            JSON.Object<CharSequence, Value> jsonObjectRoot = (JSON.Object<CharSequence, Value>) jsonRoot;
            resultObject = marshalJSONDocumentObject(jsonObjectRoot, objectClass);
        } else if (jsonRoot.type() == Value.TYPE.ARRAY) {
            JSON.Array<Value> jsonArrayRoot = (JSON.Array<Value>) jsonRoot;
            resultObject = marshalJSONDocumentArray(jsonArrayRoot, objectClass);
        } else {
            //throw new Exception();
        }
        return resultObject;
    }

    public Object marshalJSONDocumentObject(JSON.Object<CharSequence, Value> jsonObject, Class<?> objectClass) throws JSONMarshalerException {
        Object resultObject = null;

        Set<Entry<CharSequence, Value>> entrySet = jsonObject.entrySet();
        Iterator<Entry<CharSequence, Value>> entrySetIterator = entrySet.iterator();
        Entry<CharSequence, Value> entry = null;
        Inspector inspector = new Inspector(objectClass);
        inspector.inspect();

        List<MarshalerPropertyValue> marshalPropertyValues = new ArrayList<MarshalerPropertyValue>();
        while (entrySetIterator.hasNext()) {
            entry = entrySetIterator.next();
            if (entry.getKey().toString().equals(innerObjectAttribute) && inspector.hasInnerObject()) {
                /**
                 * todo: handle the inner object
                 */
            }
            String propertyName = entry.getKey().toString();
            if (inspector.hasProperty(propertyName)) {
                MarshalerPropertyValue propertyValue = new MarshalerPropertyValue(propertyName, inspector.getProperty(propertyName), entry.getValue());
                marshalPropertyValues.add(propertyValue);
            }
        }


        try {
            resultObject = objectClass.newInstance();
            for (MarshalerPropertyValue propertyValue : marshalPropertyValues) {
                if (propertyValue.getValue().isNull()) {
                    // TODO: Handle Null
                }
                InspectorProperty property = propertyValue.getProperty();
                if (property.getAccessPropertyType() == InspectorProperty.TYPE.METHOD) {
                    Method method = objectClass.getMethod(property.getMutateName(), property.getMutateInputType());
                    JavaType mutateType = JavaType.inspectObjectType(property.getMutateInputType());
                    
                    JSON.Array<Value> arrayJSON = null;
                    
                    switch (mutateType) {
                        case BOOLEAN:
                            method.invoke(resultObject, propertyValue.getValue().getBoolean());
                            break;
                        case BYTE:
                            method.invoke(resultObject, propertyValue.getValue().getNumber().byteValue());
                            break;
                        case INTEGER:
                            method.invoke(resultObject, propertyValue.getValue().getNumber().intValue());
                            break;
                        case SHORT:
                            method.invoke(resultObject, propertyValue.getValue().getNumber().shortValue());
                            break;
                        case FLOAT:
                            method.invoke(resultObject, propertyValue.getValue().getNumber().floatValue());
                            break;
                        case LONG:
                            method.invoke(resultObject, propertyValue.getValue().getNumber().longValue());
                            break;
                        case DOUBLE:
                            method.invoke(resultObject, propertyValue.getValue().getNumber().doubleValue());
                            break;
                        case STRING:
                            method.invoke(resultObject, propertyValue.getValue().getString());
                            break;
                        case ENUM:

                            break;
                        case MAP:

                            break;
                        case LIST:

                            break;
                        case OBJECT:
                            JSON.Object<CharSequence, Value> objectJSON = (JSON.Object<CharSequence, Value>) propertyValue.getValue();
                            method.invoke(resultObject, marshalJSONDocumentObject(objectJSON, property.getMutateInputType()));
                            break;
                        case ARRAY:                            
                            method.invoke(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_BYTE:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            method.invoke(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_SHORT:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            method.invoke(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_INTEGER:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            method.invoke(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_FLOAT:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            method.invoke(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_DOUBLE:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            method.invoke(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_LONG:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            method.invoke(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_STRING:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            method.invoke(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_ENUM:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            method.invoke(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_LIST:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            method.invoke(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_MAP:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            method.invoke(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        default:

                            break;
                    }
                } else if (property.getAccessPropertyType() == InspectorProperty.TYPE.FIELD) {
                    Field field = objectClass.getField(propertyValue.getProperty().getMutateName());
                    JavaType mutateType = JavaType.inspectObjectType(property.getMutateInputType());
                    
                    JSON.Array<Value> arrayJSON = null;
                    
                    switch (mutateType) {
                        case BOOLEAN:
                            field.set(resultObject, propertyValue.getValue().getBoolean());
                            break;
                        case BYTE:
                            field.set(resultObject, propertyValue.getValue().getNumber().byteValue());
                            break;
                        case INTEGER:
                            field.set(resultObject, propertyValue.getValue().getNumber().intValue());
                            break;
                        case SHORT:
                            field.set(resultObject, propertyValue.getValue().getNumber().shortValue());
                            break;
                        case FLOAT:
                            field.set(resultObject, propertyValue.getValue().getNumber().floatValue());
                            break;
                        case LONG:
                            field.set(resultObject, propertyValue.getValue().getNumber().longValue());
                            break;
                        case DOUBLE:
                            field.set(resultObject, propertyValue.getValue().getNumber().doubleValue());
                            break;
                        case STRING:
                            field.set(resultObject, propertyValue.getValue().getString());
                            break;
                        case ENUM:

                            break;
                        case MAP:

                            break;
                        case LIST:

                            break;
                        case OBJECT:
                            JSON.Object<CharSequence, Value> objectJSON = (JSON.Object<CharSequence, Value>) propertyValue.getValue();
                            field.set(resultObject, marshalJSONDocumentObject(objectJSON, property.getMutateInputType()));
                            break;
                        case ARRAY:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            field.set(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_BYTE:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            field.set(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_SHORT:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            field.set(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_INTEGER:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            field.set(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_FLOAT:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            field.set(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_DOUBLE:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            field.set(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_LONG:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            field.set(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_STRING:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            field.set(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_ENUM:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            field.set(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_LIST:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            field.set(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_MAP:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            field.set(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        case ARRAY_ARRAY:
                            arrayJSON = (JSON.Array<Value>) propertyValue.getValue();
                            field.set(resultObject, marshalJSONDocumentArray(arrayJSON, property.getMutateInputType()));
                            break;
                        default:

                            break;
                    }
                }

            }

        }
        catch (InstantiationException ex) {
            Logger.getLogger(JSONDocumentMarshaler.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch (IllegalAccessException ex) {
            Logger.getLogger(JSONDocumentMarshaler.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch (NoSuchFieldException ex) {
            Logger.getLogger(JSONDocumentMarshaler.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch (NoSuchMethodException ex) {
            Logger.getLogger(JSONDocumentMarshaler.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch (InvocationTargetException ex) {
            Logger.getLogger(JSONDocumentMarshaler.class.getName()).log(Level.SEVERE, null, ex);
        }
        return resultObject;
    }
    /*
    public Object marshalJSONDocumentArray(JSON.Array<Value> jsonArray, Class<?> objectClass) {


        return null;
    }
    */
    
    public Object marshalJSONDocumentArray(JSON.Array<Value> jsonArray, Class<?> objectClass) {
        int size = jsonArray.size();
        Object array = Array.newInstance(objectClass.getComponentType(), size);
        for (int i = 0; i < size; i++) {
            Array.set(array, i, jsonArray.get(i).getNumber().intValue());
        }
        return array;
    }
    
    /*
    public boolean[] marshalJSONDocumentIntegerArrayPrimitive(JSON.Array<Value> jsonArray, Class<?> objectClass) {
        int size = jsonArray.size();
        boolean[] array = (boolean[]) Array.newInstance(objectClass.getComponentType(), size);
        for (int i = 0; i < size; i++) {
            Array.set(array, i, jsonArray.get(i).getNumber().intValue());
        }
        return array;
    }

    public Boolean[] marshalJSONDocumentIntegerArray(JSON.Array<Value> jsonArray, Class<?> objectClass) {
        int size = jsonArray.size();
        Boolean[] array = (Boolean[]) Array.newInstance(objectClass.getComponentType(), size);
        for (int i = 0; i < size; i++) {
            Array.set(array, i, jsonArray.get(i).getNumber().intValue());
        }
        return array;
    }
    
    
    public int[] marshalJSONDocumentIntegerArrayPrimitive(JSON.Array<Value> jsonArray, Class<?> objectClass) {
        int size = jsonArray.size();
        int[] array = (int[]) Array.newInstance(objectClass.getComponentType(), size);
        for (int i = 0; i < size; i++) {
            Array.set(array, i, jsonArray.get(i).getNumber().intValue());
        }
        return array;
    }

    public Integer[] marshalJSONDocumentIntegerArray(JSON.Array<Value> jsonArray, Class<?> objectClass) {
        int size = jsonArray.size();
        Integer[] array = (Integer[]) Array.newInstance(objectClass.getComponentType(), size);
        for (int i = 0; i < size; i++) {
            Array.set(array, i, jsonArray.get(i).getNumber().intValue());
        }
        return array;
    }
    */

    public static class MarshalerPropertyValue {

        String name;
        InspectorProperty property;
        Value value;

        public MarshalerPropertyValue() {
            this.name = null;
            this.property = null;
            this.value = null;
        }

        public MarshalerPropertyValue(String name, InspectorProperty property, Value value) {
            this.name = name;
            this.property = property;
            this.value = value;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public InspectorProperty getProperty() {
            return property;
        }

        public void setProperty(InspectorProperty property) {
            this.property = property;
        }

        public Value getValue() {
            return value;
        }

        public void setValue(Value value) {
            this.value = value;
        }

        @Override
        public String toString() {
            StringBuilder resultBuilder = new StringBuilder();
            resultBuilder.append("MarshalerPropertyValue [Name:").append(name).append('|');
            resultBuilder.append("Property:").append(property);
            resultBuilder.append("->");
            resultBuilder.append("Value:").append(value).append(']');
            return resultBuilder.toString();
        }
    }
}
